概念
- Canvas:即Canvas负责将其子节点的UI元素网格合并,并生成相应的渲染命令再发送到Unity的图形管道过程。通俗来讲,Canvas就是渲染UI的组件,所以当UI变化了,它就要执行一次Batch,给GPU进行渲染。
注意Canvas的网格从CanvasRenderer组件中获取,但不包含任何子Canvas,也就是说Canvas的Batch只会影响其子节点,不会影响其子Canvas。 - Rebuild:即重新计算Graphic组件的布局和网格情的过程。
Rebuild分为Layout Rebuilds和Graphic Rebuilds
Layout Rebuilds
- 重新计算一个Layout组件子节点的位置,如HorizontalLayoutGroup。
- 通过查看UGUI源码LayoutGroup.cs脚本中SetDirty函数的调用情况,可以看到以下情况会Rebuild。
- OnEnable
- OnDidApplyAnimationProperties
- OnRectTransformDimensionsChange
- OnTransformChildrenChanged
- SetProperty
- 当LayoutGroup的直接子节点,并且是Graphic类型的(比如Image和Text),被SetLayoutDirty的时候,该LayoutGroup也会被加入到Rebuild的队列中。
Graphic Rebuilds
在Graphic这个类中可以看到,这些函数SetAllDirty、SetLayoutDirty、SetVerticesDirty和SetMaterialDirty,它们都是Rebuild,通过搜索源码发现,一定是Graphic发生了变化才会触发这些函数。例如:基本的大小、旋转以及文字的变化、图片的修改等等。
更进一步分析
无论是Layout,还是Graphic的改变,都会把本次的改变分别存储在对应的队列中,如m_LayoutRebuildQueue.AddUnique和m_GraphicRebuildQueue.AddUnique。
得出的结论:
- Layout和Graphic都会触发Rebuild。
- UGUI会在这一帧收集所有的改变,然后统一进行处理。
优化原理
用Canvas分离差异较大的Graphic。加载进来之后一直不变化的Graphic放在一个Canvas中;一直变化的如跑马灯,或者Graphic一直在做动画,那么这类Graphic放在另一个Canvas中。很明显,这样做会适量的增加DrawCall,但是Rebuild造成的危害更大(如果不是太离谱,适当的增加DrawCall并不会对CPU效率有太大的影响)。
优化方法
- 尽量少对Canvas子节点进行删除/增加、隐藏/显示。
- 尽量少对Canvas子节点进行Vertex、Rect、Color、Material、Texture等修改。因为这些操作都会触发整个Canvas的Rebuild,如果功能需求非做不可,那么就将这些节点的父节点挂上Canvas(也就是让它们能闹腾的节点自己玩去)。
- 尽量不要设计UI元素数量过多或层次结构复杂的节点结构,这样对Batch的更新速度影响会很大。所以合理的UI节点设计,不仅仅对于开发过程中寻找子节点方便,同时也使Canvas不那么频繁的刷新。